using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using data.contracts;
using generator.Properties;

namespace generator
{
    public static class ClassGenerator
    {
        public static string GenerateClass(Board board) {
            Trace.TraceInformation("  Generate class for board {0}", board.Name);
            return Resources.ClassTemplate
                .Replace("$classname$", ClassNameWithInterfaces(board))
                .Replace("$events$", GenerateBoardOutputs(board))
                .Replace("$actions$", GenerateBoardInputs(board))
                .Replace("$ctor$", GenerateCtor(board));
        }

        private static string ClassNameWithInterfaces(Board board) {
            if (board.Interface == null) {
                return board.Name + " : " + NamingRules.ToInterfaceName(board.Name);
            }
            return board.Name + " : " + board.Interface;
        }

        internal static string GenerateBoardOutputs(Board board) {
            var outputs = (from wire in board.Wires where wire.ToPin.Board == "this" 
                          select new { Pin = BoardSyntax.Pin(wire.ToPin, BoardSyntax.Direction.Output), wire.Type }).Distinct();
            return outputs
                .Aggregate("", (current, output) => current + GenerateBoardOutput(output.Pin, output.Type));
        }

        internal static string GenerateBoardInputs(Board board) {
            var inputs = (from wire in board.Wires where wire.FromPin.Board == "this" 
                         select new { Pin = BoardSyntax.Pin(wire.FromPin, BoardSyntax.Direction.Input), wire.Type }).Distinct();
            return inputs
                .Aggregate("", (current, input) => current + GenerateBoardInput(input.Pin, input.Type));
        }

        internal static string GenerateBoardOutput(string outputPin, string type) {
            return (string.IsNullOrEmpty(type) ? Resources.OutputEventTemplate_noParam : Resources.OutputEventTemplate)
                .Replace("$name$", outputPin)
                .Replace("$type$", BoardSyntax.ParsedType(type));
        }

        internal static string GenerateBoardInput(string inputPin, string type) {
            return (string.IsNullOrEmpty(type) ? Resources.InputMethodTemplate_noParam : Resources.InputMethodTemplate)
                .Replace("$name$", inputPin)
                .Replace("$type$", BoardSyntax.ParsedType(type));
        }

        private static string GenerateCtor(Board board) {
            var pins = BoardSyntax.Pins(board.Wires);
            if (pins.Count() == 0) {
                return "";
            }
            return Resources.CtorTemplate
                .Replace("$classname$", board.Name)
                .Replace("$parameters$", GenerateParameters(pins))
                .Replace("$parts$", GenerateParts(board.Wires))
                .Replace("$wiring$", GenerateWiring(board.Wires));
        }

        internal static string GenerateParameters(IEnumerable<Pin> pins) {
            return string.Join(", ",
                pins
                    .Where(pin => pin.Board != "this")
                    .Where(pin => !NamingRules.IsSpecialName(pin))
                    .Select(pin => NamingRules.ToInterfaceName(pin) + " " + NamingRules.ToVariableName(pin)));
        }

        internal static string GenerateParts(IEnumerable<Wire> wires) {
            return string.Join(Environment.NewLine,
                BoardSyntax.Pins(wires)
                    .Where(NamingRules.IsSpecialName)
                    .Select(GeneratePart));
        }

        private static string GeneratePart(Pin pin) {
            return string.Format("  var {0} = new {1}();", 
                NamingRules.ToSpecialVariableName(pin),
                NamingRules.ImplementationFor(pin));
        }

        private static string GenerateWiring(IEnumerable<Wire> wires) {
            return wires
                .Aggregate("", (current, wire) => current + GenerateWiring(wire));
        }

        internal static string GenerateWiring(Wire wire) {
            if (wire.ToPin.Board == "this") {
                if (string.IsNullOrWhiteSpace(wire.Type)) {
                    return "    " + Eventname(wire.FromPin) + " += () => " + QualifiedPinName(wire.ToPin, BoardSyntax.Direction.Output) + "();" + Environment.NewLine;
                }
                return "    " + Eventname(wire.FromPin) + " += m => " + QualifiedPinName(wire.ToPin, BoardSyntax.Direction.Output) + "(m);" + Environment.NewLine;
            }
            return "    " + Eventname(wire.FromPin) + " += " + QualifiedPinName(wire.ToPin, BoardSyntax.Direction.Input) + ";" + Environment.NewLine;
        }

        private static string Eventname(Pin pin) {
            if (pin.Board == "this") {
                return "this._" + BoardSyntax.Pin(pin, BoardSyntax.Direction.Input);
            }
            return QualifiedPinName(pin, BoardSyntax.Direction.Output);
        }

        internal static string QualifiedPinName(Pin partAndPin, BoardSyntax.Direction direction) {
            var part = Part(partAndPin);
            var pin = BoardSyntax.Pin(partAndPin, direction);
            return part + "." + pin;
        }

        private static string Part(Pin partAndPin) {
            var part = partAndPin.Board;
            if (NamingRules.IsSpecialName(part)) {
                return NamingRules.ToSpecialVariableName(part);
            }
            return NamingRules.ToVariableName(part);
        }
    }
}